﻿using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
using System.Collections;
using System.Collections.Generic;

/*
    In this demo, we demonstrate:
    1.	Automatic asset bundle dependency resolving & loading.
        It shows how to use the manifest assetbundle like how to get the dependencies etc.
    2.	Automatic unloading of asset bundles (When an asset bundle or a dependency thereof is no longer needed, the asset bundle is unloaded)
    3.	Editor simulation. A bool defines if we load asset bundles from the project or are actually using asset bundles(doesn't work with assetbundle variants for now.)
        With this, you can player in editor mode without actually building the assetBundles.
    4.	Optional setup where to download all asset bundles
    5.	Build pipeline build postprocessor, integration so that building a player builds the asset bundles and puts them into the player data (Default implmenetation for loading assetbundles from disk on any platform)
    6.	Use WWW.LoadFromCacheOrDownload and feed 128 bit hash to it when downloading via web
        You can get the hash from the manifest assetbundle.
    7.	AssetBundle variants. A prioritized list of variants that should be used if the asset bundle with that variant exists, first variant in the list is the most preferred etc.
*/

// Loaded assetBundle contains the references count which can be used to unload dependent assetBundles automatically.
namespace GBFramework
{
    public class LoadedAssetBundle
    {
        public AssetBundle m_AssetBundle;
        public int m_ReferencedCount;

        public LoadedAssetBundle(AssetBundle assetBundle)
        {
            m_AssetBundle = assetBundle;
            m_ReferencedCount = 1;
        }
    }

    public class LoadingAssetBundle
    {
        public AssetBundleCreateRequest asset;
        public int m_ReferencedCount;

        public LoadingAssetBundle(AssetBundleCreateRequest _asset)
        {
            asset = _asset;
            m_ReferencedCount = 1;
        }

        ~LoadingAssetBundle()
        {
        }
    }


    // Class takes care of loading assetBundle and its dependencies automatically, loading variants automatically.
    public class AssetBundleManager : MonoBehaviour
    {
        static string m_BaseDownloadingURL = "";
        static string[] m_Variants = { };
        public static AssetBundleManifest m_AssetBundleManifest = null;

#if UNITY_EDITOR
        static int m_SimulateAssetBundleInEditor = -1; // 0: 读取ab包， // -1:读取prefab
        const string kSimulateAssetBundles = "SimulateAssetBundles";
#endif

        //加载完成存放
        static Dictionary<string, LoadedAssetBundle> m_LoadedAssetBundles = new Dictionary<string, LoadedAssetBundle>();
        //正在被下载
        static Dictionary<string, LoadingAssetBundle> m_DownloadingWWWs = new Dictionary<string, LoadingAssetBundle>();
        //下载出错
        static Dictionary<string, string> m_DownloadingErrors = new Dictionary<string, string>();
        //下载中
        static List<AssetBundleLoadOperation> m_InProgressOperations = new List<AssetBundleLoadOperation>();
        //引用关系
        static Dictionary<string, string[]> m_Dependencies = new Dictionary<string, string[]>();
        //预加载缓存资源
        public static List<string> m_CacheAssetBundles = new List<string>();


        public static int loadAssetBundleRefCount = 0;

        // Variants which is used to define the active variants.


        List<string> keysToRemove = new List<string>();


        public static string[] Variants
        {
            get { return m_Variants; }
            set { m_Variants = value; }
        }

        // AssetBundleManifest object which can be used to load the dependecies and check suitable assetBundle variants.
        public static AssetBundleManifest AssetBundleManifestObject
        {
            set { m_AssetBundleManifest = value; }
        }

#if UNITY_EDITOR
        // Flag to indicate if we want to simulate assetBundles in Editor without building them actually.
        public static bool SimulateAssetBundleInEditor
        {
            get
            {
                if (m_SimulateAssetBundleInEditor == -1)
                    m_SimulateAssetBundleInEditor = EditorPrefs.GetBool(kSimulateAssetBundles, true) ? 1 : 0;

                return m_SimulateAssetBundleInEditor != 0;
            }
            set
            {
                int newValue = value ? 1 : 0;
                if (newValue != m_SimulateAssetBundleInEditor)
                {
                    m_SimulateAssetBundleInEditor = newValue;
                    EditorPrefs.SetBool(kSimulateAssetBundles, value);
                }
            }
        }
#endif

        // Get loaded AssetBundle, only return vaild object when all the dependencies are downloaded successfully.
        static public LoadedAssetBundle GetLoadedAssetBundle(string assetBundleName, out string error)
        {
            if (m_DownloadingErrors.TryGetValue(assetBundleName, out error))
                return null;

            LoadedAssetBundle bundle = null;
            m_LoadedAssetBundles.TryGetValue(assetBundleName, out bundle);
            if (bundle == null)
                return null;

            // No dependencies are recorded, only the bundle itself is required.
            string[] dependencies = null;
            if (!m_Dependencies.TryGetValue(assetBundleName, out dependencies))
                return bundle;

            // Make sure all dependencies are loaded
            int depLength = dependencies.Length;
            for (int i = 0; i < depLength; i++)
            {
                var dependency = dependencies[i];
                if (m_DownloadingErrors.TryGetValue(assetBundleName, out error))
                    return bundle;

                // Wait all the dependent assetBundles being loaded.
                LoadedAssetBundle dependentBundle;
                m_LoadedAssetBundles.TryGetValue(dependency, out dependentBundle);
                if (dependentBundle == null)
                    return null;
            }
            return bundle;
        }

        // Load AssetBundleManifest.
        static public AssetBundleLoadManifestOperation Initialize(string manifestAssetBundleName)
        {
            var go = new GameObject("AssetBundleManager", typeof(AssetBundleManager));
            DontDestroyOnLoad(go);

#if UNITY_EDITOR
            // If we're in Editor simulation mode, we don't need the manifest assetBundle.
            if (SimulateAssetBundleInEditor)
                return null;
#endif

            LoadAssetBundle(manifestAssetBundleName, true);
            var operation = new AssetBundleLoadManifestOperation(manifestAssetBundleName, "AssetBundleManifest", typeof(AssetBundleManifest));
            m_InProgressOperations.Add(operation);
            return operation;
        }

        // Load AssetBundle and its dependencies.
        static public void LoadAssetBundle(string assetBundleName, bool isLoadingAssetBundleManifest = false)
        {
#if UNITY_EDITOR
            // If we're in Editor simulation mode, we don't have to really load the assetBundle and its dependencies.
            if (SimulateAssetBundleInEditor)
                return;
#endif

            if (!isLoadingAssetBundleManifest)
                assetBundleName = RemapVariantName(assetBundleName);

            // Check if the assetBundle has already been processed.
            //bool isAlreadyProcessed = LoadAssetBundleInternal(assetBundleName, isLoadingAssetBundleManifest);
            LoadAssetBundleInternal(assetBundleName, isLoadingAssetBundleManifest);

            // Load dependencies.
            //if (!isAlreadyProcessed && !isLoadingAssetBundleManifest)
            if (!isLoadingAssetBundleManifest)
                LoadDependencies(assetBundleName);
        }

        // Remaps the asset bundle name to the best fitting asset bundle variant.
        static protected string RemapVariantName(string assetBundleName)
        {
            string[] bundlesWithVariant = m_AssetBundleManifest.GetAllAssetBundlesWithVariant();

            // If the asset bundle doesn't have variant, simply return.
            if (System.Array.IndexOf(bundlesWithVariant, assetBundleName) < 0)
                return assetBundleName;

            string[] split = assetBundleName.Split('.');

            int bestFit = int.MaxValue;
            int bestFitIndex = -1;
            // Loop all the assetBundles with variant to find the best fit variant assetBundle.
            for (int i = 0; i < bundlesWithVariant.Length; i++)
            {
                string[] curSplit = bundlesWithVariant[i].Split('.');
                if (curSplit[0] != split[0])
                    continue;

                int found = System.Array.IndexOf(m_Variants, curSplit[1]);
                if (found != -1 && found < bestFit)
                {
                    bestFit = found;
                    bestFitIndex = i;
                }
            }

            if (bestFitIndex != -1)
                return bundlesWithVariant[bestFitIndex];
            else
                return assetBundleName;
        }

        // Where we actuall call WWW to download the assetBundle.
        static protected void LoadAssetBundleInternal(string assetBundleName, bool isLoadingAssetBundleManifest = true)
        {
            // Already loaded.
            LoadedAssetBundle bundle = null;
            m_LoadedAssetBundles.TryGetValue(assetBundleName, out bundle);
            if (bundle != null && bundle.m_AssetBundle != null)
            {
                bundle.m_ReferencedCount++;
                return;
            }
            if (m_DownloadingWWWs.ContainsKey(assetBundleName))
            {
                LoadingAssetBundle info = m_DownloadingWWWs[assetBundleName];
                if (info != null)
                {
                    info.m_ReferencedCount++;
                }

                return;
            }

            string url = AssetBundleLoader.Instance.getBundleUrl(assetBundleName);

            AssetBundleCreateRequest asset = AssetBundle.LoadFromFileAsync(url);

            m_DownloadingWWWs.Add(assetBundleName, new LoadingAssetBundle(asset));

            loadAssetBundleRefCount++;

        }

        // Where we get all the dependencies and load them all.
        static protected void LoadDependencies(string assetBundleName)
        {
            if (m_AssetBundleManifest == null)
            {
#if LogError_Open
                Debug.LogError("Please initialize AssetBundleManifest by calling AssetBundleManager.Initialize()");
#endif
                return;
            }

            // Get dependecies from the AssetBundleManifest object..
            string[] dependencies = m_AssetBundleManifest.GetAllDependencies(assetBundleName);
            if (dependencies.Length == 0)
                return;

            for (int i = 0; i < dependencies.Length; i++)
                dependencies[i] = RemapVariantName(dependencies[i]);

            // Record and load all dependencies.(因为现在有的assetbundle资源是可选为不释放的)
            if (!m_Dependencies.ContainsKey(assetBundleName))
                m_Dependencies.Add(assetBundleName, dependencies);
            for (int i = 0; i < dependencies.Length; i++)
                LoadAssetBundleInternal(dependencies[i], false);
        }

        // Unload assetbundle and its dependencies.
        static public void UnloadAssetBundle(string assetBundleName, bool unload = true)
        {
#if UNITY_EDITOR
            // If we're in Editor simulation mode, we don't have to load the manifest assetBundle.
            if (SimulateAssetBundleInEditor)
                return;
#endif
            try
            {
                UnloadAssetBundleInternal(assetBundleName, unload);
                UnloadDependencies(assetBundleName, unload);
            }
            catch (System.Exception e)
            {
#if Log_Open
                Debug.Log("<color=red>" + e.Message + "</color>");
#endif
            }
        }

        static protected void UnloadDependencies(string assetBundleName, bool unload = true)
        {
            string[] dependencies = null;
            if (!m_Dependencies.TryGetValue(assetBundleName, out dependencies))
                return;

            // Loop dependencies.
            foreach (var dependency in dependencies)
            {
                UnloadAssetBundleInternal(dependency, unload);
            }

            m_Dependencies.Remove(assetBundleName);
        }

        static protected void UnloadAssetBundleInternal(string assetBundleName, bool unload = true)
        {
            try
            {
                string error;
                LoadedAssetBundle bundle = GetLoadedAssetBundle(assetBundleName, out error);
                if (bundle == null)
                {
                    LoadingAssetBundle info = null;
                    if (m_DownloadingWWWs.TryGetValue(assetBundleName, out info))
                    {
                        if (info != null && --info.m_ReferencedCount <= 0)
                        {
                            if (info.asset.assetBundle != null)
                            {
                                info.asset.assetBundle.Unload(true);
                            }

                            m_DownloadingWWWs.Remove(assetBundleName);

                        }
                    }
                }
                else if (--bundle.m_ReferencedCount == 0)
                {
                    if (bundle.m_AssetBundle != null)
                        bundle.m_AssetBundle.Unload(unload);
                    m_LoadedAssetBundles.Remove(assetBundleName);
                }
            }
            catch (System.Exception e)
            {
                string str = e.Message;
#if LogError_Open
                Debug.LogError("AssetBundleManager.UnloadAssetBundleInternal()  " + str);
#endif
            }
        }


        static public void UnloadRootAssetBundleInternal(string assetBundleName)
        {
            try
            {
                string error;
                LoadedAssetBundle bundle = GetLoadedAssetBundle(assetBundleName, out error);
                if (bundle != null)
                {
                    if (bundle.m_AssetBundle != null)
                        bundle.m_AssetBundle.Unload(false);
                }
            }
            catch (System.Exception e)
            {
                string str = e.Message;
#if LogError_Open
                Debug.LogError("AssetBundleManager.UnloadRootAssetBundleInternal()  " + str);
#endif
            }
        }
        void Update()
        {
            // Collect all the finished WWWs.
            keysToRemove.Clear();

            for (int i = 0; i < m_InProgressOperations.Count; )
            {
                if (!m_InProgressOperations[i].Update())
                {
                    m_InProgressOperations.RemoveAt(i);
                }
                else
                    i++;
            }

            if (m_DownloadingWWWs.Count < 1)
                return;

            var keyList = m_DownloadingWWWs.Keys;

            foreach (var key in keyList)
            {

                AssetBundleCreateRequest download = m_DownloadingWWWs[key].asset;

                // If downloading fails.
                if ((download != null && download.isDone && download.assetBundle == null) || download == null)
                {
                    if (!m_DownloadingErrors.ContainsKey(key))
                        m_DownloadingErrors.Add(key, "<color=red> download error " + key + "</color>");

                    keysToRemove.Add(key);
                    continue;
                }

                // If downloading succeeds.
                if (download.isDone)
                {
#if Log_Open
                    Debug.Log("Downloading " + key + " is done at frame " + Time.frameCount);
#endif
                    if (!m_LoadedAssetBundles.ContainsKey(key))
                        m_LoadedAssetBundles.Add(key, new LoadedAssetBundle(download.assetBundle));
                    else
                    {
                        AssetBundle ab = m_LoadedAssetBundles[key].m_AssetBundle;
                        if (ab == null)
                        {
                            m_LoadedAssetBundles[key] = new LoadedAssetBundle(download.assetBundle);
                        }

                    }
                    keysToRemove.Add(key);
                    m_LoadedAssetBundles[key].m_ReferencedCount = m_DownloadingWWWs[key].m_ReferencedCount;
                }
            }

            int removeLength = keysToRemove.Count;
            for (int i = 0; i < removeLength; i++)
            {
                m_DownloadingWWWs.Remove(keysToRemove[i]);
            }
        }

        #region 异步加载
        // Load asset from the given assetBundle.
        static public AssetBundleLoadAssetOperation LoadAssetAsync(string assetBundleName, string assetName, System.Type type, bool bSingle = true)
        {
            AssetBundleLoadAssetOperation operation = null;
#if UNITY_EDITOR
            if (SimulateAssetBundleInEditor)
            {
                operation = new AssetBundleLoadAssetOperationSimulation(assetBundleName, assetName, bSingle);
            }
            else
#endif
            {
                LoadAssetBundle(assetBundleName);
                operation = new AssetBundleLoadAssetOperationFull(assetBundleName, assetName, type);

                m_InProgressOperations.Add(operation);
            }

            return operation;
        }

        // Load level from the given assetBundle.
        static public AssetBundleLoadBaseOperation LoadLevelAsync(string assetBundleName, string levelName, bool isAdditive)
        {
            AssetBundleLoadBaseOperation operation = null;
#if UNITY_EDITOR
            if (SimulateAssetBundleInEditor)
            {
                string[] levelPaths = AssetDatabase.GetAssetPathsFromAssetBundleAndAssetName(assetBundleName, levelName);
                if (levelPaths.Length == 0)
                {
                    ///@TODO: The error needs to differentiate that an asset bundle name doesn't exist
                    //        from that there right scene does not exist in the asset bundle...
#if LogError_Open
                    Debug.LogError("There is no scene with name \"" + levelName + "\" in " + assetBundleName);
#endif
                    return null;
                }

                AssetBundleLoadLevelSimulationOperation temp = new AssetBundleLoadLevelSimulationOperation();
                if (isAdditive)
                    temp.m_sceneRequest = EditorApplication.LoadLevelAdditiveAsyncInPlayMode(levelPaths[0]);
                else
                    temp.m_sceneRequest = EditorApplication.LoadLevelAsyncInPlayMode(levelPaths[0]);

                operation = temp;

            }
            else
#endif
            {
                LoadAssetBundle(assetBundleName);
                operation = new AssetBundleLoadLevelOperation(assetBundleName, levelName, isAdditive);

                m_InProgressOperations.Add(operation);
            }

            return operation;
        }
        #endregion

        public static void StopAllProgressOperations()
        {
            int nProgressLength = m_InProgressOperations.Count;
            for (int i = 0; i < nProgressLength; i++)
            {
                m_InProgressOperations[i].isStop = true;

                UnloadAssetBundle(m_InProgressOperations[i].m_AssetBundleName);

            }
            m_InProgressOperations.Clear();
        }

        public static void ClearAllAssetBundles()
        {
            foreach (var one in m_LoadedAssetBundles)
            {
                one.Value.m_AssetBundle.Unload(false);
            }
            m_LoadedAssetBundles.Clear();
            m_DownloadingErrors.Clear();
        }
    } // End of AssetBundleManager.
}